#pragma once

#include "ast.hpp"

class DeclAST: public DefaultNullAST {
	public:
		enum Type {
			TypeConstDecl = 0,
			TypeVarDecl
		} type;

		unique_ptr<BaseAST> sub_decl;

		string *Dump(ostream &out) const override {
			__TAB__;
			switch (type) {
				case TypeConstDecl: return sub_decl->Dump(out);
				case TypeVarDecl: return sub_decl->Dump(out);
				default: assert(false);
			}
		}
};

class BTypeAST: public DefaultNullAST {
	public:
		unique_ptr<string> type_name;

		string *Dump(ostream &out) const override {
			__TAB__;
			return new string(*type_name);
		}
};

class ConstDeclAST: public DefaultNullAST {
	public:
		unique_ptr<BaseAST> btype;
		unique_ptr<vector<unique_ptr<BaseAST>>> const_defs;
		
		string *Dump(ostream &out) const override {
			__TAB__;
			auto type_name = unique_ptr<string>(btype->Dump(out));
			if (type_name->compare("i32") == 0) {
				for (auto const_def = const_defs->begin(); const_def != const_defs->end(); ++const_def) {
					auto const_ident = unique_ptr<string>((*const_def)->Dump(out, IdentType::CONSTI32));
				}
				return nullptr;
			} else {
				cerr << "Wrong type of const " << *type_name << endl;
				exit(-1);
			}
		}
};

class ConstDefAST: public DefaultNullAST {
	public:
		unique_ptr<string> ident;
		unique_ptr<BaseAST> const_init_val;

		string *Dump(ostream &out, const IdentType &ident_type) const override {
			auto val = unique_ptr<string>(const_init_val->Dump(out));
			symtab_manager.Push(*ident);
			symtab_manager.SetType(*ident, ident_type);
			symtab_manager.SetVal(*ident, *val);
			return new string(*ident);
		}
};

class ConstInitValAST: public DefaultNullAST {
	public:
		unique_ptr<BaseAST> const_exp;

		string *Dump(ostream &out) const override {
			return const_exp->Dump(out);
		}
};

class VarDeclAST: public DefaultNullAST {
	public:
		unique_ptr<BaseAST> btype;
		unique_ptr<vector<unique_ptr<BaseAST>>> var_defs;

		string *Dump(ostream &out) const override {
			__TAB__;
			auto type_name = unique_ptr<string>(btype->Dump(out));

			if (type_name->compare("i32") == 0) {
				for (auto var_def = var_defs->begin(); var_def != var_defs->end(); ++var_def) {
					auto res = unique_ptr<string>((*var_def)->Dump(out, IdentType::VARI32));
				}
				return nullptr;
			} else {
				cerr << "Wrong type:" << *type_name << endl;
				exit(-1);
			}
		}
};

class VarDefAST: public DefaultNullAST {
	public:
		enum Type {
			TypeDecl = 0,
			TypeDef
		} type;
		unique_ptr<string> ident;
		unique_ptr<BaseAST> init_val;

		string *Dump(ostream &out, const IdentType &ident_type) const override {
			symtab_manager.Push(*ident);
			symtab_manager.SetType(*ident, ident_type);

			const SymTabEntry &entry = symtab_manager.Get(*ident);
			switch(type) {
				case TypeDef: {
					switch(entry.type) {
						case IdentType::VARI32: {
							out << "  " << entry.name << " = alloc " << "i32" << endl;
							auto val = unique_ptr<string>(init_val->Dump(out));
							out << "  store " << *val << ", " << entry.name << endl;
							return new string(*ident);
						}
						default: assert(false);
					}
				}
				case TypeDecl: {
					switch(entry.type) {
						case IdentType::VARI32: {
							out << "  " << entry.name << " = alloc " << "i32" << endl;
							return new string(*ident);
						}
						default: assert(false);
					}
				}
			}
		}
};

class InitValAST: public DefaultNullAST {
	public:
		unique_ptr<BaseAST> exp;

		string *Dump(ostream &out) const override {
			return exp->Dump(out);
		}
};

